//==========================================================================
// Copyright (c) 2000-2008,  Elastos, Inc.  All Rights Reserved.
//==========================================================================
#include "ddk.h"

#include <stdio.h>
#include <time.h>
#include <ctype.h>
#include <malloc.h>
#include <misc.h>
#include <hash.h>
#include <llist.h>
#include <abc.h>
#include <fat.h>


fat::fat():m_nRef(0)
{
    pFat12 = NULL;
    pFat16 = NULL;
    pFat32 = NULL;
    p_Sharedata = NULL;
    Ktfs_Init_Lock(&this->fat_lock);
}

fat::~fat()
{
    if (pFat32) {
        delete pFat32;
    }
    else if (pFat16) {
        delete pFat16;
    }
    else if (pFat12) {
        delete pFat12;
    }
    if (p_Sharedata)
        p_Sharedata->Release();

    Ktfs_Term_Lock(&this->fat_lock);
}

UInt32 fat::AddRef()
{
    return InterlockedIncrement((PInt32)&m_nRef);
}

UInt32  fat::Release()
{
    UInt32 Refs;

    Refs = InterlockedDecrement((PInt32)&m_nRef);
    if (Refs == 0) {
        delete this;
    }
    return Refs;
}

/*
 * fat_loadfat()
 *    Load FAT handling
 *
 * Read in the FAT, convert if needed.
 * Extract some useful values from the boot block at the same time.
 */
int fat::
fat_loadfat(class fatfs_sharedata *pSharedata)
{
    int ret = -1;

    if (p_Sharedata->fat_size == 32) {
        ret = pFat32->fat32_loadfat(p_Sharedata);
    }
    else if (p_Sharedata->fat_size == 16) {
        ret = pFat16->fat16_loadfat(p_Sharedata);
    }
    else if (p_Sharedata->fat_size == 12) {
        ret = pFat12->fat12_loadfat(p_Sharedata);
    }

    return ret;
}

/*
 * fat_checkmbr()
 *    Check mbr and get the first partition
 */
UInt32 fat::
fat_checkmbr(char *pBuf)
{
    UInt32 begSec;
    if ((UInt8)pBuf[510] != 0x55 || (UInt8)pBuf[511] != 0xAA)
        return 0;
    memcpy(&begSec, pBuf+0x1c6, 4);

    return begSec;
}

/*
 * fat_init()
 *    Initialize FAT handling
 *
 * Read in the FAT, convert if needed.
 * Extract some useful values from the boot block at the same time.
 */
int fat::
fat_init(class fatfs_sharedata *pSharedata)
{
    int ret = 1;

//    ASSERT_DEBUG(pSharedata != NULL, "fat_setlen: bad clust");
    if (pSharedata == NULL) {
        return 1;
    }
    p_Sharedata = pSharedata;
    p_Sharedata->AddRef();

    /*
     * Calculate some static values
     */
//    ASSERT_DEBUG((p_Sharedata->bootb.BPB_BytsPerSec0 +
//                 (p_Sharedata->bootb.BPB_BytsPerSec1<<8)) == SECSZ,
//                 "fat_init: bad sector size");
    DEBUGOUT(
        kprintf("BytePerSec0:%d, BytePerSec1:%d, RootEntCnt0:%d, RootEntCnt1:%d,\
               TotSec16_0:%d, TotSec16_1:%d, TotSec32:%d, SecPerCls:%d, fatsz16:%d,\
               ResvdSecCnt:%d, NumFATs:%d.\n",
               p_Sharedata->bootb.BPB_BytsPerSec0, p_Sharedata->bootb.BPB_BytsPerSec1,
               p_Sharedata->bootb.BPB_RootEntCnt0, p_Sharedata->bootb.BPB_RootEntCnt1,
               p_Sharedata->bootb.BPB_TotSec16_0, p_Sharedata->bootb.BPB_TotSec16_1,
               p_Sharedata->bootb.BPB_TotSec32, p_Sharedata->bootb.BPB_SecPerClus,
               p_Sharedata->bootb.BPB_FATSz16, p_Sharedata->bootb.BPB_ResvdSecCnt,
               p_Sharedata->bootb.BPB_NumFATs);
    )

    p_Sharedata->secsz = (p_Sharedata->bootb.BPB_BytsPerSec0 +
        (p_Sharedata->bootb.BPB_BytsPerSec1<<8));

    if (512 == p_Sharedata->secsz) p_Sharedata->secshift = 9;
    else if (1024 == p_Sharedata->secsz) p_Sharedata->secshift = 10;
    else if (2048 == p_Sharedata->secsz) p_Sharedata->secshift = 11;
    else {
        p_Sharedata->secsz = 512; // 512 bytes is default
        DEBUGOUT(kprintf("Don't support %d bytes sector size", p_Sharedata->secsz);)
        return 1;
    }

    p_Sharedata->clsize = p_Sharedata->bootb.BPB_SecPerClus * p_Sharedata->secsz;

    /*
     * Figure out FAT size
     */
    if (p_Sharedata->bootb.BPB_FATSz16 == 0) {
        /*
         * If the FAT size in the base of the boot sector is 0,
         * we know it's FAT32 (and the actual FAT size is in a
         * FAT32 extension, which the FAT32 fatops will handle).
         */
        p_Sharedata->fat_size = FAT32;
        pFat32 = new fat32;
    }
    else {
        /*
         * Figure out the filesystem size.
         * This also fills in "p_Sharedata->nclust" and "p_Sharedata->data0"
         * on behalf of the FAT12/16 code.
         */
        UInt32 x = p_Sharedata->bootb.BPB_TotSec16_0 +
               (p_Sharedata->bootb.BPB_TotSec16_1 << 8);
        p_Sharedata->dirents = (p_Sharedata->bootb.BPB_RootEntCnt1<<8) +
               p_Sharedata->bootb.BPB_RootEntCnt0;
        p_Sharedata->data0 = p_Sharedata->bootb.BPB_ResvdSecCnt +
               (p_Sharedata->bootb.BPB_NumFATs * p_Sharedata->bootb.BPB_FATSz16)
               + (p_Sharedata->dirents * sizeof(struct directory)) / p_Sharedata->secsz;

        if (x > 0) {
            p_Sharedata->nclust =
                (x - p_Sharedata->data0) / p_Sharedata->bootb.BPB_SecPerClus;
        }
        else {
            p_Sharedata->nclust = (p_Sharedata->bootb.BPB_TotSec32 -
                p_Sharedata->data0) / p_Sharedata->bootb.BPB_SecPerClus;
        }

        /*
         * Below this threshold, it's FAT12.  Otherwise FAT16
         */
        if (p_Sharedata->nclust < 4085) {
            pFat12 = new fat12;
            p_Sharedata->fat_size = FAT12;
        }
        else {
            pFat16 = new fat16;
            p_Sharedata->fat_size = FAT16;
        }
    }

    /*
     * FAT format-specific init
     */
    if (p_Sharedata->fat_size == 32) {
        ret = pFat32->fat32_init(p_Sharedata);
    }
    else if (p_Sharedata->fat_size == 16) {
        ret = pFat16->fat16_init(p_Sharedata);
    }
    else if (p_Sharedata->fat_size == 12) {
        ret = pFat12->fat12_init(p_Sharedata);
    }

    return ret;
}

/*
 * clust_setlen()
 *    Twiddle FAT allocation to match the indicated length
 *
 * Returns 0 if it could be done; 1 if it failed.
 */
int fat::
clust_setlen(struct clust *c, UInt32 newclust)
{
    int ret = 1;

    /*
     * No change, so no problem
     */
    if (c->c_nclust == newclust) {
        return(0);
    }

    FatLock();

    if (p_Sharedata->fat_size == 32) {
        ret = pFat32->fat32_setlen(c, newclust);
    }
    else if (p_Sharedata->fat_size == 16) {
        ret = pFat16->fat16_setlen(c, newclust);
    }
    else if (p_Sharedata->fat_size == 12) {
        ret = pFat12->fat12_setlen(c, newclust);
    }

    FatUnlock();

    return(ret);
}

/*
 * alloc_clust()
 *    Allocate a description of the given cluster chain
 */
struct clust * fat::
alloc_clust(struct directory *d)
{
    struct clust *c = NULL, *c2 = NULL;

    /*
     * Get the cluster description
     */

    c = (struct clust *)malloc(sizeof(struct clust));
    if (c == 0) {
        return(0);
    }

    /*
     * Zero-length file is easy
     */
    if ((d == 0) || (START(d) == 0)) {//How to deal with MS OS'Fat32 root????
        c->c_nclust = 0;
        c->c_clust = 0;
        c->n_newclust = 0x7fffffff;
        return(c);
    }

    FatLock();

    /*
     * Let FAT specific code take over
     */
    if (p_Sharedata->fat_size == 32) {
        c2 = pFat32->fat32_alloc(c, d);
    }
    else if (p_Sharedata->fat_size == 16) {
        c2 = pFat16->fat16_alloc(c, d);
    }
    else if (p_Sharedata->fat_size == 12){
        c2 = pFat12->fat12_alloc(c, d);
    }

    FatUnlock();

    /*
     * On failure, dump the clust struct
     */
    if (c2 == NULL) {
        free(c);
    }

    return(c2);
}

/*
 * free_clust()
 *    Free a cluster description
 */
void fat::
free_clust(struct clust *c)
{
    if (c->c_clust) {
        free(c->c_clust);
    }
    free(c);
    c = NULL;
}

/*
 * fat_sync()
 *    Sync out the FAT to disk
 *
 * Both copies are updated; if the first copy can not be written
 * successfully, the second is left alone and the server aborts.
 */
void fat::
fat_sync(void)
{
    /*
     * Not dirty--no work
     */
    if (!p_Sharedata->fat_dirty) {
        return;
    }

    FatLock();

    if (p_Sharedata->fat_size == 32) {
        pFat32->fat32_sync();
    }
    else if (p_Sharedata->fat_size == 16) {
        pFat16->fat16_sync();
    }
    else if (p_Sharedata->fat_size == 12) {
        pFat12->fat12_sync();
    }

    FatUnlock();

    /*
     * Now it's clean
     */
    p_Sharedata->fat_dirty = 0;
}

/*
 * get_clust()
 *    Get cluster # of given cluster slot
 *
 * Used to fill in the "start" field of dir entries like "..",
 * as well as to support inode number generation.
 */
UInt32 fat::
get_clust(struct clust *c, UInt32 idx)
{
    ASSERT_DEBUG(c->c_nclust > idx, "get_clust: bad index");
    return(c->c_clust[idx]);
}

UInt64 fat::
fat_calcfreespace()
{
    UInt64 totalFreeByte = 0;
    FatLock();

    if (p_Sharedata->fat_size == 32) {
        totalFreeByte = pFat32->fat32_calcfreespace();
    }
    else if (p_Sharedata->fat_size == 16) {
        totalFreeByte = pFat16->fat16_calcfreespace();
    }
    else if (p_Sharedata->fat_size == 12) {
        totalFreeByte = pFat12->fat12_calcfreespace();
    }

    FatUnlock();
    return totalFreeByte;
}

#if defined(_DEBUG)
void fat::fat_stat(UInt32 *Fat_secs)
{
    FatLock();

    if (p_Sharedata->fat_size == 32)
        pFat32->fat32_stat(Fat_secs);
    else if (p_Sharedata->fat_size == 16)
        pFat16->fat16_stat(Fat_secs);
    else if (p_Sharedata->fat_size == 12)
        pFat12->fat12_stat(Fat_secs);

    FatUnlock();
}
#endif

inline void fat::FatLock()
{
    Ktfs_Lock(&this->fat_lock);
}

inline void fat::FatUnlock()
{
    Ktfs_Unlock(&this->fat_lock);
}
